﻿using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Timers;
using System.Windows;
using System.Windows.Threading;
using WpfGenetic.Decoders;
using WpfGenetic.Tests;

namespace WpfGenetic
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly object _lockObj = new();    // Общий объект блокировки для алгоритма дешифровки
        private readonly ManualResetEventSlim _manualResetEvent;     // Событие для отслеживания прекращения остановки алгоритма дешифровки

        private readonly GeneticConfiguration _config; // Начальная конфигурация

        // Токены для задачи дешифровки алгоритма Виженера
        private CancellationTokenSource decodeCancellationTokenSource;
        private CancellationToken decodeCancellationToken;

        // Объект логгера данных
        private readonly ScottPlot.Plottables.DataLogger _logger;

        // Таймер, который обновляет время работы алгоритма
        private readonly DispatcherTimer _dispatcherTimer;

        // Секундомер для замера времени работы алгоритма
        private readonly Stopwatch _stopwatch;
        // Последняя запись о времени работы таймера
        private TimeSpan _ts;

        /// <summary>
        /// Основная функции создания окна
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            _manualResetEvent = new ManualResetEventSlim(false);    // Изначально не активен

            string configPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Config/GeneticConfig.json");
            _config = GeneticConfigCreator.Factory(configPath);

            decodeCancellationTokenSource = new CancellationTokenSource();

            // Настраиваем ComboBox
            UpdateComboBoxSettings();

            // Настраиваем график
            _logger = plot_GraphTopFitness.Plot.Add.DataLogger();
            _logger.ViewFull();
            // По умолчанию отключено взаимодействие с мышью
            plot_GraphTopFitness.Interaction.Disable();
            plot_GraphTopFitness.Plot.Axes.Left.Label.Text = "Фитнесс-функция";
            plot_GraphTopFitness.Plot.Axes.Bottom.Label.Text = "Поколение";

            // Инициализация таймеров
            _dispatcherTimer = new DispatcherTimer();
            _dispatcherTimer.Tick += new EventHandler(StopwatchUpdate);
            _dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            _stopwatch = new Stopwatch();

            //Parsing.ParseBigramTest();
            //MessageBox.Show("Запущен тест!");
            //Loaded += StartTests;
        }

        #region Обработка событий

        private void ComboBox_CipherType_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            _config.CIPHER_TYPE = cb_CipherType.SelectedIndex;
        }

        /// <summary>
        /// Обрабатывает нажатие кнопки дешифровки текста
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void DecryptButton_Click(object sender, RoutedEventArgs e)
        {
            if (_config != null && cryptoTextBox.Text.Length > 0)
            {
                decryptButton.IsEnabled = false;
                cryptoTextBox.IsReadOnly = true;
                cancelDecryptButton.IsEnabled = true;      
                stopDecryptButton.IsEnabled = true;

                decodeCancellationToken = decodeCancellationTokenSource.Token;

                // Запускаем таймеры
                _dispatcherTimer.Start();
                _stopwatch.Start();

                try
                {
                    var txt = cryptoTextBox.Text;

                    // Очистка графика
                    ClearGraphTopFitness();

                    if (_config.CIPHER_TYPE == 0)
                    {
                        await Task.Run(() => { Vigenere.Start(cryptoText: txt, config: (GeneticConfiguration)_config.Clone(), text: out string text,
                                                                lockObj: _lockObj, token: decodeCancellationToken, logDelegate: SaveLog,
                                                                resultDelegate: SaveResult, topFitnessDelegate: UpdateGraphTopFitness,
                                                                getTimeNow: GetStopwatchTime); }, decodeCancellationToken);
                    }
                    else if(_config.CIPHER_TYPE == 1)
                    {
                        await Task.Run(() => { IntegerPermutation.Start(cryptoText: txt, config: (GeneticConfiguration)_config.Clone(), text: out string text,
                                                                        lockObj: _lockObj, token: decodeCancellationToken, logDelegate: SaveLog,
                                                                        resultDelegate: SaveResult, topFitnessDelegate: UpdateGraphTopFitness); }, decodeCancellationToken);
                    }

                }
                catch(OperationCanceledException)
                {
                    MessageBox.Show("Дешифровка отменена");
                }
                catch (AggregateException ae)
                {
                    foreach (Exception ex in ae.InnerExceptions)
                    {
                        if (ex is TaskCanceledException)
                            MessageBox.Show("Дешифровка отменена");
                        else
                            MessageBox.Show(ex.Message);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
                finally
                {
                    // Останавливаем таймеры
                    _dispatcherTimer.Stop();
                    _stopwatch.Reset();

                    stopDecryptButton.IsEnabled = false;
                    cancelDecryptButton.IsEnabled = false;
                    cryptoTextBox.IsReadOnly = false;
                    decryptButton.IsEnabled = true;
                }
            }
        }
        
        /// <summary>
        /// Закрывает таску с дешифровкой шифра Виженера через decodeCancellationToken
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CancelDecryptButton_Click(object sender, RoutedEventArgs e)
        {
            decodeCancellationTokenSource.Cancel();

            // На случай если перед этим алгоритм был остановлен необходимо снять блокировку
            _manualResetEvent.Set();
            continueDecryptButton.IsEnabled = false;
            _manualResetEvent.Reset();

            // Обновляем источник токенов, чтобы следующий токен автоматически не закрылся
            decodeCancellationTokenSource.Dispose();
            decodeCancellationTokenSource = new CancellationTokenSource();
        }

        /// <summary>
        /// Приостанавливает выполнение дешифровки путём блокировки общего ресурса
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void StopDecryptButton_Click(object sender, RoutedEventArgs e)
        {
            stopDecryptButton.IsEnabled = false;
            continueDecryptButton.IsEnabled = true;
            await Stop();
        }

        /// <summary>
        /// Функция продолжает работу алгоритма после её приостановки
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ContinueDecryptButton_Click(object sender, RoutedEventArgs e)
        {
            _manualResetEvent.Set();
            continueDecryptButton.IsEnabled = false;
            _manualResetEvent.Reset();
            stopDecryptButton.IsEnabled = true;
        }

        private void ClearLogButton_Click(object sender, RoutedEventArgs e)
        {
            logTextBox.Text = "";
        }

        private void SettingsButton_Click(object sender, RoutedEventArgs e)
        {
            SettingsWindow settingsWindow = new(_config)
            {
                Owner = this
            };

            settingsWindow.ShowDialog();
        }

        #endregion


        #region Вспомогательные функции

        private void StopwatchUpdate(object? sender, EventArgs e)
        {
            if (_stopwatch.IsRunning)
            {
                _ts = _stopwatch.Elapsed;

                stopwatchValue.Text = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                                                    _ts.Hours, _ts.Minutes, _ts.Seconds,
                                                    _ts.Milliseconds / 10);
            }
        }

        /// <summary>
        /// Возвращает текущее значение таймера
        /// </summary>
        /// <returns></returns>
        private string GetStopwatchTime()
        {
            if (_stopwatch.IsRunning)
            {
                TimeSpan ts = _stopwatch.Elapsed;

                return String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                                                    ts.Hours, ts.Minutes, ts.Seconds,
                                                    ts.Milliseconds / 10);
            }

            return "";
        }

        /// <summary>
        /// Добавление нового значения в график лучшей фитнесс функции
        /// </summary>
        /// <param name="newValues"></param>
        private void UpdateGraphTopFitness(double newValues)
        {
            _logger.Add(newValues);
            plot_GraphTopFitness.Plot.Title($"Обработано {_logger.Data.CountTotal:N0} поколений");
            plot_GraphTopFitness.Refresh();
        }

        private void ClearGraphTopFitness()
        {
            _logger.Data.Clear();
            plot_GraphTopFitness.Plot.Title($"Обработано {_logger.Data.CountTotal:N0} поколений");
            plot_GraphTopFitness.Refresh();
        }

        private void UpdateComboBoxSettings()
        {
            if (cb_CipherType.Items.Count > 0)
            {
                if (_config.CIPHER_TYPE > cb_CipherType.Items.Count - 1)
                {
                    _config.CIPHER_TYPE = cb_CipherType.Items.Count - 1;
                }
                else if (_config.CIPHER_TYPE < 0)
                {
                    _config.CIPHER_TYPE = 0;
                }

                cb_CipherType.SelectedIndex = _config.CIPHER_TYPE;
            }
        }

        /// <summary>
        /// Вспомогательная таска для асинхронной остановки алгоритма
        /// </summary>
        /// <returns></returns>
        private Task Stop()
        {
            return Task.Run(() =>
            {
                lock (_lockObj)
                {
                    _manualResetEvent.Wait();
                }
            });
        }

        /// <summary>
        /// Добавляет данные в лог
        /// </summary>
        /// <param name="log"></param>
        private async void SaveLog(string log)
        {
            log = "Время работы: " + String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                                                    _ts.Hours, _ts.Minutes, _ts.Seconds,
                                                    _ts.Milliseconds / 10)
                + "\n" + log;

            await Dispatcher.InvokeAsync(() => { logTextBox.AppendText(log); });
        }

        /// <summary>
        /// Добавляет данные в результат
        /// </summary>
        /// <param name="result"></param>
        private async void SaveResult(string result)
        {
            result = "Время работы: " + String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                                                    _ts.Hours, _ts.Minutes, _ts.Seconds,
                                                    _ts.Milliseconds / 10)
                + "\n" + result;

            await Dispatcher.InvokeAsync(() => {  resultTextBox.Text = result; });
        }

        private async void StartTests(object sender, RoutedEventArgs e)
        {
            await Task.Run(() => {
                _dispatcherTimer.Start();
                _stopwatch.Start();

                
                Testing.UnitTest1(config: _config, testsPath: "Tests2.txt", topFitnessDelegate: UpdateGraphTopFitness,
                                  clearGraphDelegate: ClearGraphTopFitness, logDelegate: SaveLog, SType: 0);
                
                /*
                Testing.UnitTest2(config: _config, testsPath: "TestsKey.txt", topFitnessDelegate: UpdateGraphTopFitness,
                                  clearGraphDelegate: ClearGraphTopFitness, logDelegate: SaveLog, SType: 0);
                */
                /*
                Testing.UnitTest3(config: _config, testsPath: "TestsPopulation.txt", topFitnessDelegate: UpdateGraphTopFitness,
                                  clearGraphDelegate: ClearGraphTopFitness, logDelegate: SaveLog, SType: 0);
                */
                _stopwatch.Reset();
            });
        }

        #endregion
    }
}